package org.wheel.base.stmt;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.io.SAXReader;
import org.wheel.base.utils.ExpressionUtils;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author klaus.wang
 * @date 2018/4/2 华小勇 22:57
 */
public class OrmSqlUtils {

    static final String NS = "namespace";

    static Map<String, SqlStatement> statements = new HashMap<>();
    
    static Set<String> entities = new HashSet<>();

    static Map<String, Function<Object[], Object[]>> prefixFnMap = new HashMap<>();
    static Map<String, Function<Object[], Object[]>> suffixFnMap = new HashMap<>();

    static {
        Function<Object[], Object[]> fn = (objs) -> {
            org.dom4j.Element ele = (org.dom4j.Element) objs[0];
            String test = ele.attributeValue("test");
            Boolean value = StringUtils.isNotBlank(test) && ExpressionUtils.getValue(test, objs[2], Boolean.class);
            objs[1] = value != null && value == true;
            return objs;
        };
        prefixFnMap.put("if", fn);
        // when TODO
        prefixFnMap.put("when", fn);

        prefixFnMap.put("#text", (objs) -> {
            org.dom4j.Node node = (org.dom4j.Node) objs[0];
            String text = node.getText();
            if (StringUtils.isNotBlank(text)) {
                ((StringBuffer) objs[1]).append(" ").append(text.trim()).append(" ");
            }
            return objs;
        });

        prefixFnMap.put("include", (objs) -> {
            Object includeNode = null;
            org.dom4j.Element ele = (org.dom4j.Element) objs[0];
            String refid = ele.attributeValue("refid");
            if (StringUtils.isNotBlank(refid)) {
                refid = String.format("%s.%s", ele.attributeValue(NS), refid);
                includeNode = statements.get(refid).getElement();
            }
            if ((boolean) (objs[1] = (includeNode != null))) {
                objs[0] = includeNode;
            }
            return objs;
        });

        suffixFnMap.put("set", (objs) -> {
            autoTrim((StringBuffer) objs[1], " SET", "", ",", ",");
            return objs;
        });

        suffixFnMap.put("where", (objs) -> {
            autoTrim((StringBuffer) objs[1], " WHERE", "", "AND|OR", "");
            return objs;
        });

        suffixFnMap.put("trim", (objs) -> {
            StringBuffer csb = StringBuffer.class.cast(objs[1]);
            if (csb.length() == 0) {
                return objs;
            }
            org.dom4j.Element node = org.dom4j.Element.class.cast(objs[0]);
            // 给sql语句拼接的前缀
            String prefix = node.attributeValue("prefix");

            // 给sql语句拼接的后缀
            String suffix = node.attributeValue("suffix");

            // 去除sql语句前面的关键字或者字符，该关键字或者字符由prefixOverrides属性指定，假设该属性指定为"AND"，当sql语句的开头为"AND"，trim标签将会去除该"AND"
            String prefixOverrides = node.attributeValue("prefixOverrides");

            // 去除sql语句后面的关键字或者字符，该关键字或者字符由suffixOverrides属性指定
            String suffixOverrides = node.attributeValue("suffixOverrides");

            autoTrim(csb, prefix, suffix, prefixOverrides, suffixOverrides);

            return objs;
        });

    }

    /**
     * 加入xml sql
     *
     * @param is
     * @param firstName
     * @throws Exception
     */
    public static void add(InputStream is, String firstName) throws Exception {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        dbf.setNamespaceAware(true);

        SAXReader reader = new SAXReader();

        org.dom4j.Document dom = reader.read(is);
        
        org.dom4j.Element ele = dom.getRootElement();

        create(ele, null);

    }

    public static void add(InputStream is) throws Exception {
        add(is, "repository");
    }

    public static void add(File file) throws Exception {
        try (InputStream is = new FileInputStream(file)) {
            add(is);
        } catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * 迭代节点放入缓存
     * 
     * namespace + "." +  id
     *
     * @param ele
     */
    public static void create(org.dom4j.Element ele, String ns) {
        if (ns == null && (ns = ele.attributeValue(NS)) != null) {
        }
        String id = ele.attributeValue("id");
        if (StringUtils.isNotBlank(id)) {
            statements.put(String.format("%s.%s", ns, id), new SqlStatement(ele));
        } else if ("class".equals(ele.getName())) {
        	entities.add(ele.asXML());
        	return;
        }
        ele.addAttribute(NS, ns);
        Iterator<org.dom4j.Element> it = ele.elementIterator();
        while (it.hasNext()) {
            create(it.next(), ns);
        }
    }

    /**
     * 构建语句
     *
     * @param node
     * @param psb
     * @param root
     */
    public static boolean build(org.dom4j.Node node, StringBuffer psb, Object root) {
        String nodeName = Optional.ofNullable(node.getName()).orElse("#text");
        Object[] params = new Object[]{node, psb, root};
        prefixFnMap.computeIfAbsent(nodeName, key -> Function.identity()).apply(params);
        if (params[1] instanceof Boolean && !((boolean) params[1])) {
            return false;
        }
        /**
         * 子节点sql
         */
        StringBuffer csb = new StringBuffer();
        org.dom4j.Node n = (org.dom4j.Node) params[0];
        if (n.hasContent()) {
            for (Object node1 : ((org.dom4j.Element) n).content()) {
                /**
                 * TODO choose when
                 */
                boolean rst = build((org.dom4j.Node)node1, csb, root);
            }
        }
        suffixFnMap.computeIfAbsent(nodeName, key -> Function.identity()).apply(new Object[]{node, csb, root});
        psb.append(csb);
        return true;
    }

    /**
     * 字符串处理
     *
     * @param csb
     * @param prefix
     * @param suffix
     * @param prefixOverrides
     * @param suffixOverrides
     */
    static void autoTrim(StringBuffer csb, String prefix, String suffix, String prefixOverrides,
                         String suffixOverrides) {
        String cstr = csb.toString().trim();
        csb.setLength(0);

        /**
         * 前缀 AND|OR 去掉
         */
        if (StringUtils.isNotBlank(prefixOverrides)) {
            for (String po : prefixOverrides.split("\\|")) {
                if (StringUtils.startsWithIgnoreCase(cstr, po)) {
                    cstr = cstr.substring(po.length());
                }
            }
        }

        /**
         * 后缀 , 去掉
         */
        if (StringUtils.isNotBlank(suffixOverrides)) {
            for (String so : suffixOverrides.split("\\|")) {
                if (StringUtils.endsWithIgnoreCase(cstr, so)) {
                    cstr = cstr.substring(0, cstr.length() - so.length());
                }
            }
        }

        /**
         * 前缀 添加 eg: insert 语句 values(
         */
        if (StringUtils.isNotBlank(cstr) && StringUtils.isNotBlank(prefix)) {
            if (!StringUtils.startsWithIgnoreCase(cstr, prefix)) {
                csb.append(prefix).append(" ");
            }
        }

        csb.append(cstr);
        /**
         * 后缀 添加 eg: insert 语句 最后添加右括号
         */
        if (StringUtils.isNotBlank(suffix)) {
            if (!StringUtils.endsWithIgnoreCase(cstr, suffix)) {
                csb.append(" ").append(suffix);
            }
        }
    }

    public static SqlStatement getById(String id) {
        return statements.get(id);
    }

    public static boolean has(String id) {
        return statements.containsKey(id);
    }

    public static void callEntities(Consumer<String> consumer) {
    	
    	consumer.accept(String.join("\n", entities));
    }
    
}
